/**
 * @class MapService
 * @extends OpenLayers.Class
 * API utilizada para gerenciar mapa OpenLayers.
 * 
 * Exemplo de uso:
 *
 *     window.mapService = new MapService({
 *         map : new OpenLayers.Map({
 *             div           : 'map',
 *             projection    : new OpenLayers.Projection("EPSG:4326"),
 *             maxExtent     : new OpenLayers.Bounds(-180.0,-90.0,180.0,90.0),
 *             theme         : null,
 *             controls      : []
 *         })
 *     });
 */
MapService = OpenLayers.Class({
	/**
     * @cfg {OpenLayers.Map}
     * Mapa que será gerenciado pelo MapService
     */
	map               : null,
	
	controls          : {},
	
	systemLayers      : {},
	
	/**
     * @cfg {Object}
     * Define posição inicial do mapa.
     * 
     * Utilizado pelo método {@link MapService#zoomToHome zoomToHome}.
     * @param {OpenLayers.Bounds} [home=new OpenLayers.Bounds(-77.913548594742, -32.569978703903, -29.222142344742, 5.047208796097)] Posição inicial do mapa centralizado no Brasil
     */
	home              : new OpenLayers.Bounds(-77.913548594742, -32.569978703903, -29.222142344742, 5.047208796097),

    /**
     * @cfg {OpenLayers.Events}
     * Utilizado para registrar eventos.
     *
     * Exemplo:
     *
     *     mapService.events.register('eventname', this, function(){
     *         //code
     *     });
     *
     */
	events            : null,
	
	EVENT_TYPES       : [
         /**
          * @event
          * Disparado quando um controle é ativado/desativado.
          * @param {Object} object
          * @param {OpenLayers.Control} object.deactivated Controle desativado.
          * @param {OpenLayers.Control} object.activated Controle ativado.
          */
         'ontogglecontrol',
         
         /**
          * @event
          * Disparado toda vez que passar o mouse sobre uma feição e o mapa estiver no modo "SelectMode".
          * @param {Object} object
          * @param {Object} object.feature Feição com foco no mapa.
          * @param {Object} object.px Pixel relativo a posição do mouse.
          */
         'onshowtooltip',
         
         /**
          * @event
          * Disparado toda vez em que uma feição que estava com foco, perde o foco e o mapa estiver no modo "SelectMode". 
          * @param {Object} object
          * @param {Object} object.feature Feição que perdeu o foco.
          */
         'onhidetooltip',
         
         /**
          * @event
          * Disparado quando feições forem selecionadas no mapa.
          * @param {Object} object
          * @param {Object} object.features Itens adicionados na seleção.
          */
         'onfeaturesselected',
         
         /**
          * @event
          * Disparado quando a seleção do mapa for removida.
          * @param {Object} object
          * @param {Object} object.features Itens removidos da seleção.
          */
         'onfeaturesunselected',
         
         /**
          * @event
          * Disparado quando o mapa estiver carregando algum layer
          * @param {Object} object
          * @param {boolean} object.busy indica se o mapa está ocupado ou não.
          */
         'onmapbusychange'
         ],
	
    initialize   : function(options){
    	var me = this;
    	OpenLayers.Util.extend(me, options);
    	
    	me.events = new OpenLayers.Events(me, null, me.EVENT_TYPES);
		if(me.eventListeners instanceof Object) {
			me.events.on(me.eventListeners);
		};

		
		//LAYERS DE SISTEMA
    	me.systemLayers.digitalizeLayer = new OpenLayers.Layer.Vector("Digitalize Layer",{
    		displayInLayerSwitcher : false,
    		alwaysInRange: true
    	});
    	
    	me.systemLayers.selectionLayer = new OpenLayers.Layer.Vector("Selection Layer", {
    		displayInLayerSwitcher : false,
    		alwaysInRange: true,
    		style: OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['select'], {
    			cursor: "inherit"
    		})
    	});
    	
    	for(var layer in me.systemLayers){
    		me.map.addLayer(me.systemLayers[layer]);
    	};
    	
    	me.map.events.register('addlayer', me, function(){
    		me.map.setLayerIndex(me.systemLayers.digitalizeLayer, me.map.layers.length);
    		me.map.setLayerIndex(me.systemLayers.selectionLayer, me.map.layers.length);
    	});
    	//LAYERS DE SISTEMA
    	
    	
    	//CONTROLES INTERNOS
    	me.controls.pan = new OpenLayers.Control.Navigation({
			displayClass  : 'ux-icon-panmode',
			type          : OpenLayers.Control.TYPE_TOOL
		});
    	
    	
    	me.controls.zoomBox = new OpenLayers.Control.ZoomBox({
			displayClass  : 'ux-icon-zoomboxmode',
			type          : OpenLayers.Control.TYPE_TOOL
		});
    	
    	
    	me.controls.navigationHistory = new OpenLayers.Control.NavigationHistory();
        
        me.controls.measureDistance = new MapService.Control.MeasureDistance();
        
    	me.controls.measureArea = new MapService.Control.MeasureArea();
    	
    	me.controls.printMap = new MapService.Control.PrintMap();

    	me.controls.slider = new MapService.Control.Slider();

    	me.controls.digitalizePoint = new OpenLayers.Control.DrawFeature(
    		me.systemLayers.digitalizeLayer,
    		OpenLayers.Handler.Point,
    		{
    			displayClass : 'ux-icon-digitalize-point',
    			type           : OpenLayers.Control.TYPE_TOOL
    		}
    	);
    	
    	me.controls.digitalizeLine = new OpenLayers.Control.DrawFeature(
    		me.systemLayers.digitalizeLayer,
			OpenLayers.Handler.Path,
			{
    			displayClass   : 'ux-icon-digitalize-line',
    			type           : OpenLayers.Control.TYPE_TOOL
    		}
    	);
    	
    	me.controls.digitalizePolygon = new OpenLayers.Control.DrawFeature(
    		me.systemLayers.digitalizeLayer,
    		OpenLayers.Handler.Polygon,
    		{
    			displayClass   : 'ux-icon-digitalize-polygon',
    			type           : OpenLayers.Control.TYPE_TOOL
    		}
    	);
    	
    	me.controls.digitalizeRectangle = new OpenLayers.Control.DrawFeature(
    		me.systemLayers.digitalizeLayer, 
    		OpenLayers.Handler.RegularPolygon, 
    		{
    			displayClass   : 'ux-icon-digitalize-rectangle',
    			handlerOptions : {sides: 4, irregular: true},
    			type           : OpenLayers.Control.TYPE_TOOL
    		}
    	);
    	
    	me.controls.modify = new OpenLayers.Control.ModifyFeature(
    		me.systemLayers.digitalizeLayer,
    		{
    			type           : OpenLayers.Control.TYPE_TOOL,
    			toggle         : false,
    			standalone     : true
    		}
    	);
    	me.modifyFeatureComplete = function(event){
    	};
    	me.systemLayers.digitalizeLayer.events.on({
            "afterfeaturemodified": function(event){
            	me.modifyFeatureComplete(event);
            }
        });
    	
        me.controls.selectFeature = new MapService.Control.SelectFeature({
        	box            : true,
	    	hover          : true,
	    	multipleKey    : "shiftKey",
	    	toggleKey      : "ctrlKey",
	        click	       : true,
	        single         : true,
	        clickout       : true,
	        toggle         : true,
	        clickTolerance : 15
        });
		me.controls.selectFeature.events.register("featuresselected", this, function(e) {
			me.clearSelection();
			me.systemLayers.selectionLayer.addFeatures(e.features);
			
			me.events.triggerEvent("onfeaturesselected", {
				features: e.features
	    	});
		});
		me.controls.selectFeature.events.register("clickout", this, function(e) {
			me.clearSelection();
		});
		me.controls.selectFeature.events.register("hoverfeature", this, function(e) {
			me.events.triggerEvent("onshowtooltip", {
	    		feature : e.feature,
	    		px      : e.object.handlers.hover.px
	    	});
		});
		me.controls.selectFeature.events.register("outfeature", this, function(e) {
			me.events.triggerEvent("onhidetooltip", {
	    		feature: e.feature
	    	});
		});
		me.map.events.register("mousemove", this, function(e) {
			//verifica se mudou a posição do mouse
			var pixelTolerance = 3;
			var lastXY = me.controls.selectFeature.lastHoverXY;
			var xy = e.xy;
			me.controls.selectFeature.lastHoverXY = xy;
			
			if(pixelTolerance && lastXY) {
	            var dxy = Math.sqrt(Math.pow(lastXY.x - xy.x, 2) + Math.pow(lastXY.y - xy.y, 2));
	            
	            if(dxy < pixelTolerance) {
	                return false;
	            }
	        }
	        
			//se a posição do mouse mudou fecha o tooltip
			if(me.controls.selectFeature.active && me.controls.selectFeature.hoverFeature){
				me.events.triggerEvent("onhidetooltip", {
		    		feature: me.controls.selectFeature.hoverFeature
		    	});
				me.controls.selectFeature.hoverFeature = null;
			}
		});
		
    	for(var control in me.controls){
    		me.map.addControl(me.controls[control]);
    	};
    	//CONTROLES INTERNOS
    	
    	
    	//EVENTO onmapbusychange
		me.layersLoading = 0;
		var addLayerLoading = function(){
			me.layersLoading++;
			me.events.triggerEvent("onmapbusychange", {busy: true});
		};
		
		var removeLayerLoading = function(){
			me.layersLoading--;
			if(me.layersLoading <= 0){
				me.layersLoading = 0;
				me.events.triggerEvent("onmapbusychange", {busy: false});
			};
		};
		
		var addLayerBusyEvent = function(layer){
			layer.events.register("loadstart", me, function(){
				addLayerLoading();
			});
			
			layer.events.register("loadend", me, function(){
				removeLayerLoading();
			});
			
			layer.events.register("removed", me, function(){
				layer.events.unregister("loadstart", me, function(){
					addLayerLoading();
				});
				
				layer.events.unregister("loadend", me, function(){
					removeLayerLoading();
				});
			});
		}
		
		me.map.events.register('addlayer', me, function(event){
			addLayerBusyEvent(event.layer);
        });

		for(var i = 0; i < me.map.layers.length; i++){
			addLayerBusyEvent(me.map.layers[i]);
    	};
    	//EVENTO onmapbusychange
    	
    	
    	me.map.mapService = me;
    },
    
    
    
    //FUNCOES INTERNAS
    toggleControl : function(control){
    	var me = this;
    	
    	for(var key in me.controls) {
    		if(me.controls[key].active && me.controls[key].type === OpenLayers.Control.TYPE_TOOL){
   				me.lastActiveControl = me.controls[key];	
    			me.controls[key].deactivate();
    		};
	    };
	    
	    if(control && control.type === OpenLayers.Control.TYPE_TOOL){
	    	control.activate();
	    	me.events.triggerEvent("ontogglecontrol", {
	    		deactivate : me.lastActiveControl,
	    		activate   : control
	    	});
	    };
	},
	
	getActiveControlTool : function(){
		var me = this;
		
    	for(var key in me.controls) {
    		if(me.controls[key].active && me.controls[key].type === OpenLayers.Control.TYPE_TOOL){
    			return me.controls[key];
    		};
	    };
	},
	//FUNCOES INTERNAS
	
	
	
	//API
	/**
     * Altera o mapa para o modo "Pan".
    */
    panMode : function(){
    	var me = this;
    	
    	me.toggleControl(me.controls.pan);
    	return true;
    },

	/**
     * Altera o mapa para o modo "Selecionar itens".
    */
	selectMode : function(){
		var me = this;
		
		me.toggleControl(me.controls.selectFeature);
		return true;
	},

	/**
     * Altera o mapa para o modo "Zoom Box".
    */
    zoomBoxMode : function(){
    	var me = this;
    	
    	me.toggleControl(me.controls.zoomBox);
	    return true;
    },

	/**
     * Zoom inicial do mapa.
     * 
     * Valores definidos na propriedade {@link MapService#home home}.
    */
	zoomToHome : function(){
		var me = this;
		
		var extent = me.home.clone();
		
		me.map.zoomToExtent(extent.transform('EPSG:4326', me.map.getProjection()));
		
		//força atualização do mapa.
		me.map.setCenter(me.map.getCenter(), me.map.zoom, true, true);
	},

	/**
     * Zoom anterior já navegado.
    */
    zoomPrevious : function(){
    	var me = this;
    	
    	me.controls.navigationHistory.previous.trigger();
    },
    
	/**
     * Próximo zoom já navegado.
    */
    zoomNext : function(){
    	var me = this;
    	
    	me.controls.navigationHistory.next.trigger();
    },

	/**
     * Altera o mapa para o modo de "Medição de Comprimento".
    */
	measureDistance : function(){
		var me = this;
		me.toggleControl(me.controls.measureDistance);
	    return true;
	},
	
	/**
     * Altera o mapa para o modo de "Medição de Área".
    */
	measureArea : function(){
		var me = this;
		me.toggleControl(me.controls.measureArea);
	    return true;
	},
	
	/**
     * Atualiza o mapa, força a atualização dos layers ativos.
    */
	refreshMap : function(){
		var me = this;
		me.map.updateSize();
		
		var layers = me.map.layers;
		for(var i = 0; i < layers.length; i++){
			var layer = layers[i];
			if(layer.mergeNewParams){
				layer.mergeNewParams({dc: new Date().getTime()});	
			};
			layer.redraw();
		}
	},
	
	/**
     * Criar Pontos.
     * 
     * Exemplo:
     *     
     *     mapService.digitalizePoint(function(geometry){
     *         console.log(geometry);
     *     });
     *     
     * @param {Function} callback Função de retorno executada após a conclusão.
     * @param {OpenLayers.Geometry} callback.geometry Geometria criada.
    */
    digitalizePoint: function(callback){
    	var me = this;
    	
    	var lastActiveControl = me.getActiveControlTool();
    	lastActiveControl.deactivate();
    	me.systemLayers.digitalizeLayer.destroyFeatures();
		var control = me.controls.digitalizePoint;
		
		me.controls.digitalizePoint.featureAdded = function(){
			var geometry = control.layer.features[0].geometry.clone();
			me.toggleControl(lastActiveControl);
			me.systemLayers.digitalizeLayer.destroyFeatures();
			
			if(callback){
				callback(geometry);	
			};
		};
		
		me.controls.pan.activate();
		control.activate();
	},
	
	/**
     * Criar Polilinhas.
     * 
     * Exemplo:
     *     
     *     mapService.digitalizeLine(function(geometry){
     *         console.log(geometry);
     *     });
     *     
     * @param {Function} callback Função de retorno executada após a conclusão.
     * @param {OpenLayers.Geometry} callback.geometry Geometria criada.
    */
	digitalizeLine: function(callback){
		var me = this;
    	
		var lastActiveControl = me.getActiveControlTool();
		lastActiveControl.deactivate();
    	me.systemLayers.digitalizeLayer.destroyFeatures();
		var control = me.controls.digitalizeLine;
		
		control.featureAdded = function(){
			var geometry = control.layer.features[0].geometry.clone();
			me.toggleControl(lastActiveControl);
			me.systemLayers.digitalizeLayer.destroyFeatures();
			
			if(callback){
				callback(geometry);	
			};
		};
		
		me.controls.pan.activate();
		control.activate();
	},

	/**
     * Criar Polígonos.
     * 
     * Exemplo:
     *     
     *     mapService.digitalizePolygon(function(geometry){
     *         console.log(geometry);
     *     });
     *     
     * @param {Function} callback Função de retorno executada após a conclusão.
     * @param {OpenLayers.Geometry} callback.geometry Geometria criada.
    */
	digitalizePolygon: function(callback){
		var me = this;
    	
		var lastActiveControl = me.getActiveControlTool();
		lastActiveControl.deactivate();
    	me.systemLayers.digitalizeLayer.destroyFeatures();
		var control = me.controls.digitalizePolygon;
		
		control.featureAdded = function(){
			var geometry = control.layer.features[0].geometry.clone();
			me.toggleControl(lastActiveControl);
			me.systemLayers.digitalizeLayer.destroyFeatures();
			
			if(callback){
				callback(geometry);	
			};
		};
		
		me.controls.pan.activate();
		control.activate();
	},

	/**
     * Criar retângulos.
     * 
     * Exemplo:
     *     
     *     mapService.digitalizeRectangle(function(geometry){
     *         console.log(geometry);
     *     });
     *     
     * @param {Function} callback Função de retorno executada após a conclusão.
     * @param {OpenLayers.Geometry} callback.geometry Geometria criada.
    */
	digitalizeRectangle : function(callback){
		var me = this;
    	
		var lastActiveControl = me.getActiveControlTool();
		lastActiveControl.deactivate();
    	me.systemLayers.digitalizeLayer.destroyFeatures();
		var control = me.controls.digitalizeRectangle;
		
		control.featureAdded = function(){
			var geometry = control.layer.features[0].geometry.clone();
			me.toggleControl(lastActiveControl);
			me.systemLayers.digitalizeLayer.destroyFeatures();
			
			if(callback){
				callback(geometry);	
			};
		};
		
		control.activate();
	},
	
	/**
     * Modifica uma geometria.
     * 
     * Exemplo de uso:
     * 
     * Criar geometria:
     *     
     *     mapService.digitalizePolygon(function(geometry){
     * 	    window.geometry = geometry;
     *     });
     *     
     *     
     * Modificar vértices:
     *     
     *     mapService.modifyGeometry({
     * 	    geometry : window.geometry,
     *         callback : function(newGeometry){
     *            console.log(newGeometry);
     *         }
     *     });
     *     
     * Mover Geometria:
     * 
     *     mapService.modifyGeometry({
     * 	    geometry : window.geometry,
     *         drag     : true,
     *         callback : function(newGeometry){
     *            console.log(newGeometry);
     *         }
     *     });
     *     
     *     
     * Rotacionar Geometria:
     * 
     *     mapService.modifyGeometry({
     * 	    geometry : window.geometry,
     *         rotate   : true,
     *         callback : function(newGeometry){
     *            console.log(newGeometry);
     *         }
     *     });
     *     
     *     
     * Redimensionar Geometria:
     * 
     *     mapService.modifyGeometry({
     * 	    geometry : window.geometry,
     *         resize   : true,
     *         callback : function(newGeometry){
     *            console.log(newGeometry);
     *         }
     *     });
     *     
     *     
     * Redimensionar mudando a proporção:
     * 
     *     mapService.modifyGeometry({
     * 	    geometry        : window.geometry,
     *         resize          : true,
     * 	    keepAspectRatio : false,
     *         callback        : function(newGeometry){
     *            console.log(newGeometry);
     *         }
     *     });
     *     
     *     
     * @param {Object} options
     * @param {OpenLayers.Geometry} options.geometry Geometria a ser editada.
     * @param {Boolean} [options.rotate=false] options.rotate (optional) Permite rotacionar a geometria.
     * @param {Boolean} [options.resize=false] options.resize (optional) Permite redimensionar a geometria.
     * @param {Boolean} [options.drag=false] options.drag (optional) Permite mover a geometria.
     * @param {Boolean} [options.keepAspectRatio=true] options.keepAspectRatio (optional) Mantêm a proporção da geometria ao redimensionar.
     * @param {Function} options.callback Função de retorno executada após concluir a edição.
     * @param {OpenLayers.Geometry} options.callback.geometry Geometria editada.
    */
	modifyGeometry : function(options){
		var me = this;
		
		options = OpenLayers.Util.extend({}, options);
		var lastActiveControl = me.getActiveControlTool();
		if(lastActiveControl) {
			lastActiveControl.deactivate();
		}
		var control = me.controls.modify;
		
		if(options.geometry){
			me.systemLayers.digitalizeLayer.destroyFeatures();
			var feature = new OpenLayers.Feature.Vector(options.geometry);
			
			me.systemLayers.digitalizeLayer.addFeatures([feature]);
			
			var callbackFnc = OpenLayers.Function.Void;
			if(options.callback){
				callbackFnc = options.callback;
			}
			
			
			// reset modification mode
			control.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
			
			if(options.rotate){
				control.mode |= OpenLayers.Control.ModifyFeature.ROTATE;
			};
			
			if(options.resize){
				control.mode |= OpenLayers.Control.ModifyFeature.RESIZE;
				
				//default true
				if(options.keepAspectRatio == undefined){
					options.keepAspectRatio = true;
				}
				
				if(options.keepAspectRatio){
					control.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
				};
			};
			
			if(options.drag){
				control.mode |= OpenLayers.Control.ModifyFeature.DRAG;
			};
			
			if(options.rotate || options.drag){
				control.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
			};
			
			control.selectFeature(feature);
			
			me.modifyFeatureComplete = function(event){
				me.modifyFeatureComplete = OpenLayers.Function.Void;
				
				if(event && event.feature && event.feature.geometry){
					var geometry = event.feature.geometry.clone();
					me.toggleControl(lastActiveControl);
					me.systemLayers.digitalizeLayer.destroyFeatures();
					
					callbackFnc(geometry);
				}
			};
			
			me.controls.pan.activate();
			control.activate();
		}else{
			throw new Error('Favor informar a geometria a ser editada!!!');
		};
	},
	
	/**
     * Limpa os itens selecionados no mapa
    */
	clearSelection : function(){
		var me = this;
		
		if(me.systemLayers.selectionLayer.features.length > 0){
			me.systemLayers.selectionLayer.destroyFeatures();
			
			me.events.triggerEvent("onfeaturesunselected", {
	    		features: me.systemLayers.selectionLayer.features
	    	});
		}
	},
	
	/**
     * Imprime a visão atual do mapa.
    */
    printMap : function(){
    	var me = this;
    	me.controls.printMap.trigger();
    },
    //API
	
    CLASS_NAME: "MapService"
});


MapService.Control = MapService.Control || {};
MapService.Format = MapService.Format || {};
MapService.Layer = MapService.Layer || {};